package org.hamcrest.collection; import static org.hamcrest.core.IsEqual.equalTo; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.hamcrest.Description; import org.hamcrest.Factory; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeDiagnosingMatcher; public class IsIterableContainingInOrder<E> extends TypeSafeDiagnosingMatcher<Iterable<E>> { private final List<Matcher<? super E>> matchers; public IsIterableContainingInOrder(List<Matcher<? super E>> matchers) { this.matchers = matchers; } @Override protected boolean matchesSafely(Iterable<E> iterable, Description mismatchDescription) { MatchSeries<E> matchSeries = new MatchSeries<E>(matchers, mismatchDescription); for (E item : iterable) { if (! matchSeries.matches(item)) { return false; } } return matchSeries.isFinished(); } public void describeTo(Description description) { description.appendText("iterable over ").appendList("[", ", ", "]", matchers); } private static class MatchSeries<F> { public final List<Matcher<? super F>> matchers; private final Description mismatchDescription; public int nextMatchIx = 0; public MatchSeries(List<Matcher<? super F>> matchers, Description mismatchDescription) { this.mismatchDescription = mismatchDescription; if (matchers.isEmpty()) { throw new IllegalArgumentException("Should specify at least one expected element"); } this.matchers = matchers; } public boolean matches(F item) { return isNotSurplus(item) && isMatched(item); } public boolean isFinished() { if (nextMatchIx < matchers.size()) { mismatchDescription.appendText("No item matched: ").appendDescriptionOf(matchers.get(nextMatchIx)); return false; } return true; } private boolean isMatched(F item) { Matcher<? super F> matcher = matchers.get(nextMatchIx); if (!matcher.matches(item)) { describeMismatch(matcher, item); return false; } nextMatchIx++; return true; } private boolean isNotSurplus(F item) { if (matchers.size() <= nextMatchIx) { mismatchDescription.appendText("Not matched: ").appendValue(item); return false; } return true; } private void describeMismatch(Matcher<? super F> matcher, F item) { mismatchDescription.appendText("item " + nextMatchIx + ": "); matcher.describeMismatch(item, mismatchDescription); } } @Factory public static <E> Matcher<Iterable<E>> contains(E... items) { List<Matcher<? super E>> matchers = new ArrayList<Matcher<? super E>>(); for (E item : items) { matchers.add(equalTo(item)); } return contains(matchers); } @Factory public static <E> Matcher<Iterable<E>> contains(final Matcher<E> item) { return contains(new ArrayList<Matcher<? super E>>() {{ add(item); }}); } @Factory public static <E> Matcher<Iterable<E>> contains(Matcher<? super E>... items) { return contains(Arrays.asList(items)); } @Factory public static <E> Matcher<Iterable<E>> contains(List<Matcher<? super E>> contents) { return new IsIterableContainingInOrder<E>(contents); } }